#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<pthread.h>
#include<limits.h>
#include<string.h>

#include"../headers/groups.h"
#include"../headers/drawing.h"
#include"../headers/server_msg.h"

#define SERVER_PORT 5000
#define MAX_USER_INVITE 20
#define MAX_THREAD_ALLOWED 100
#define timeOut 10 //60 secondes
/*
	PROGRAMME PRINCIPALE DU SERVEUR
*/

/*Déclaration global*/
int sd;

struct invite
{
	int list[MAX_USER_INVITE];			// Liste des invités
	struct sockaddr_in addr[MAX_USER_INVITE];	// Adresse des invités
	time_t timeList[MAX_USER_INVITE];		// Time out des invités
};

struct currentThread
{
	pthread_t list[MAX_THREAD_ALLOWED];
	int descriptorList[MAX_THREAD_ALLOWED];
};

struct server_msg msg;
struct list_groups myList;
struct invite myInvite;
struct currentThread myThread;

/*Procédure qui envoit un message aux clients au groupe grp*/
int sendAllUser(char *buff, int nsd)
{
	/* Le format pour la réception d'un message au serveur doit être de la manière suivante: MESSAGE:le_text_sans_: exemple: MESSAGE:bonjour*/
	int i, j;
	char message[100];
	struct user *select=NULL;

	// Recherche du groupe
	for(i=0; i<myList.nGroup && ((select=selectUserBySD(&myList.list[i], nsd))==NULL); ++i);
	if(i>=myList.nGroup) return 0;
	
	sprintf(message, "%s\n", buff);
	for(j=0; j<myList.list[j].nUser; ++j)
	{
		write(myList.list[i].list[j].sd, message, sizeof(message));
	}
	
	sprintf(message, "SendAllUser() function:: MESSAGE delivered at group Name=%s from user NAME=%s !", myList.list[i].name, select->name);
	sendMSG(msg.fd, message);
}

int query(char *buffer, int nsd)
{
	//On découpe la chaine
	int n=0, continu=1, m=0, j, i=0;
	char decomp[20][100];
	char *tmp;
	char str[100];
	strcpy(str, buffer);

	tmp=strtok(str, ":");
	while(tmp!=NULL)
	{
		strcpy(decomp[n], tmp);
		tmp=strtok(NULL, ":");
		n++;
	}

	if(n<1) return 0;

	if(strcmp(decomp[0], "MESSAGE")==0)
	{
		if(n>2) return 0;
		return sendAllUser(decomp[1], nsd);
	}	
}
/* Procédure de déconnection d'un client */
void disconnect(struct groups *grp, struct user u)
{
	write(u.sd, "deconnexion\n", 12);
	int isMaster=0, i, max=INT_MAX;
	char name[100], buffer[100], user[100];

	if(strcmp(grp->master.name, u.name)==0)
	{
		isMaster=1;
	}

	rmUser(grp, u);
	close(u.sd);	

	if(isMaster) // Si c'est le chef qui quitte le groupe
	{
		if(grp->nUser==0) //Si il est seul
		{
			//On supprime le groupe
			rmGroup(&myList, grp->name);
		}
		else //Sinon on cherche la personne qui est venu juste après
		{
			for(i=0; i<grp->nUser; ++i)
			{
				if(grp->list[i].id<max)
				{ 
					max=grp->list[i].id;
					strcpy(name, grp->list[i].name);
				}
			}
		
			//On affecte le nouveau chef de groupe
			struct user *u=selectUserByName(grp, name);
			strcpy(grp->master.name, u->name);
			grp->master.id=u->id;
			grp->master.sd=u->sd;
			grp->master.addr=u->addr;

			sprintf(buffer, "Disconnect() function:: user name=%s is now master of group name=%s ! ", u->name, grp->name);
			sendMSG(msg.fd, buffer); 
		}
	}

	sprintf(buffer, "User name=%s id=%d ip=%s is disconnected successfully! ", u.name, u.id, inet_ntoa(u.addr.sin_addr));
	sendMSG(msg.fd, buffer);		
}

/* La fonction analyse toutes les chaines transmises au serveur. Ainsi le serveur ne prendra pas en compte la chaine reçue si c'est une chaine de dialogue client<=>serveur*/
int reservedName(char *buf)
{
	if(strcmp("deconnexion", buf)==0 || strcmp("CREATE_GROUP", buf)==0 || strcmp("JOIN_GROUP", buf)==0) return 0;
	else return 1;

}

int initClient(int nsd)
{
	int i, k, nbr, j, invalideName=0, groupFull=0;;
	char connect[100], info[100], num[10], cmd[100], groupName[100], clientName[100], list[100];
	struct user *u=(struct user*)malloc(sizeof(u));
			
	//Envoit au client la liste des groupes
	write(nsd, "Current server group: \n", 23);
	sprintf(num, "%d\n", myList.nGroup);
	write(nsd, num, strlen(num));
	
	for(k=0; k<myList.nGroup; ++k)
	{
		sprintf(list, "Name=%s Master=%s User count=%d/%d\n", myList.list[k].name, myList.list[k].master.name, myList.list[k].nUser, maxUser);		
		write(nsd, list, strlen(list));
	}
	write(1, list, strlen(list));

	//On attend la requète du client: le client peut soit créer un groupe, soit rejoindre un groupe existant
	//fcntl(nsd, F_SETFL, O_NONBLOCK); //On rend la fonction read non blocante
	// Si on lis quelque chose
	if((nbr=read(nsd, cmd, sizeof(cmd)))!=0)
	{ 
		cmd[nbr]='\0';
		write(nsd, "SUCCESS1\n", 9);
		//Le client veut créer un groupe
		if(strcmp(cmd, "CREATE_GROUP")==0)
		{
			if((nbr=read(nsd, clientName, sizeof(clientName)))!=-1)
			{
				write(nsd, "SUCCESS2\n", 9);
				clientName[nbr]='\0';
				
				//Lecture du nom du groupe
				if((nbr=read(nsd, groupName, sizeof(groupName)))!=-1) 
				{
					groupName[nbr]='\0';
					write(nsd, "SUCCESS3\n", 9);

					//On teste si il peut <=> le nombre de groupe dans la liste doit être inférieur ou égale au nombre max de groupe authorisé 						//par la liste.
					if(myList.nGroup==maxGroup)
					{ 
						if(write(nsd, "INVALIDE_GROUP\n", 15)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write invalide group to client! ");
						sendMSG(msg.fd, "InitClient() function:: List group full! ");
						return 0;
					}
					else
					{
						//On teste si le groupe n'existe pas déjà
						struct groups *grp=selectGroup(&myList, groupName);
						if(grp!=NULL)
						{ 
							char groupExist[100];
							if(write(nsd, "INVALIDE_GROUP\n", 15)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write invalide group to client! ");
							sprintf(info, "InitClient() function:: Group=%s already existe! ", groupName);
							sendMSG(msg.fd, info);
							return 0;	
						}
						else //On peut créer le groupe, ajouter l'utilisateur et ajouter le groupe à la liste
						{
							struct groups *grp=(struct groups*)malloc(sizeof(struct groups));
							if(reservedName(clientName)==0) strcpy(clientName, "First");
							createUser(u, clientName, myInvite.addr[i], nsd);
							createGroup(grp, groupName, u, msg);
				
							addUser(grp, *u);

							//On l'enlève de la liste des invités
							myInvite.list[i]=-1;
			
							if(addGroup(&myList, *grp))
							{
								if(write(nsd, "VALIDE_GROUP\n", 13)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write valide group to client! ");
								
								free(grp);
								return 1;
							}
							else
							{
								if(write(nsd, "INVALIDE_GROUP\n", 15)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write valide group to client! ");
								
								free(grp);
								return 0;
							}
						}
					}
				}
				else
				{ 
					sendMSG(msg.fd, "InitClient() function:: cannot read group name! ");
					write(nsd, "NOT_SUCCESS10\n", 14);
					return 0;
				}
			}
			else 
			{
				write(nsd, "NOT_SUCCESS8\n", 13);
				sendMSG(msg.fd, "InitClient() function:: cannot read user name! ");
				return 0;
			}
		}
		else if(strcmp(cmd, "JOIN_GROUP")==0) //Le client veut rejoindre un groupe
		{
			if((nbr=read(nsd, clientName, sizeof(clientName)))!=-1)
			{
				write(nsd, "SUCCESS3\n", 9);
				clientName[nbr]='\0';
				//Lecture du nom du groupe
				if((nbr=read(nsd, groupName, sizeof(groupName)))!=-1) 
				{
					write(nsd, "SUCCESS4\n", 9);
					groupName[nbr]='\0';
					//Recherche si le groupe existe
					struct groups *grp=selectGroup(&myList, groupName);
					if(grp==NULL) //Il n'existe pas :'( on sort =>[]
					{ 
						if(write(nsd, "GROUP_NAME_INVALIDE\n", 20)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write invalide name to client! ");

						sprintf(info, "InitClient() function:: Group=%s not existe! ", groupName);
						sendMSG(msg.fd, info);
						return 0;	
					}	
					else //Il existe, on veut se joindre à la fête à condition qu'il n'y a pas déjà une personne avec le même pseudo ...
					{
						//Recherche de mon amonyme
						struct user *select=selectUserByName(grp, clientName);
						if(grp->nUser !=0 && select!=NULL) //Le pauvre, il s'est fait piqué sa place, le client doit changer de pseudo
						{
							if(write(nsd, "INVALIDE_NAME\n", 14)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write invalide name to client! ");
							return 0;
						}
						else //Le nom n'y est pas, on l'ajoute au groupe
						{
							if(reservedName(clientName)==0)
							{ 
								strcpy(clientName, "Unknow");
								sprintf(num, "%d", grp->nUser+1);
								strcat(clientName, num);
							}

							createUser(u, clientName, myInvite.addr[i], nsd);
							if(addUser(grp, *u))
							{ 
								if(write(nsd, "VALIDE_NAME\n", 12)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write valide name to client! ");
								//sendMembers(u);

								//On l'enlève de la liste des invités
								myInvite.list[i]=-1;
								return 1;
							}
							else
							{	
								if(write(nsd, "INVALIDE_NAME\n", 14)==-1) sendMSG(msg.fd, "InitClient() function:: cannot write valide name to client! ");
								return 0;							
							}
						}
					}
				}
				else
				{ 
					sendMSG(msg.fd, "InitClient() function:: cannot read group name! ");
					write(nsd, "NOT_SUCCESS7\n", 13);
					return 0;
				}
			}
			else
			{ 
				sendMSG(msg.fd, "InitClient() function:: cannot read user name! ");
				write(nsd, "NOT_SUCCESS6\n", 13);
				return 0;
			}
		}
		else
		{ 
			sendMSG(msg.fd, "InitClient() function:: CREATE_GROUP or JOIN_GROUP problem! ");
			write(nsd, "NOT_SUCCESS5\n", 13);
			return 0;
		}
	return 0;
	}
free(u);
return 0;
}

/* Ajouter un utilisateur */
void *manageClient(void *socket)
{
	int nsd=(int)socket, continu, nbr, i;
	char buffer[100];
	struct user *select;
	continu=initClient(nsd); //Soit le client rejoint un groupe, soit il crée un groupe
;
	if(continu)
	{
		//En attente d'une action a executer
		while(continu!=0)
		{
			nbr=read(nsd, buffer, sizeof(buffer)); 
			buffer[nbr]='\0';
			query(buffer, nsd);
			continu=0;
		}
	}
	else //L'initialisation du client n'a pas pu se faire
	{
		//On retire l'invité de la liste
		for(i=0; i<MAX_USER_INVITE && (myInvite.list[i]!=nsd); ++i);
		myInvite.list[i]=-1;

		//On retire le thread de la liste
		for(i=0; i<MAX_THREAD_ALLOWED && (myThread.descriptorList[i]!=nsd); ++i)
		myThread.list[i]=0;

		sprintf(buffer, "ManageClient() function:: Invite nsd=%d has been released from the current invite list! ", nsd);
		sendMSG(msg.fd, buffer);
		close(nsd);
	} 
}

void inter_sigInt()
{
	/*Déconnexion des clients*/
	int i,j;
	for(i=0; i<myList.nGroup; ++i)
	{
		for(j=0; j<myList.list[i].nUser; ++j)
		{
			disconnect(&myList.list[i], myList.list[i].list[j]);
		}
	}

	close(sd);
	sendMSG(msg.fd, "Inter_sigInt() function:: Server shutdown successfully! ");
	exit(0);
}

void checkTimeOut()
{
	int i, j, nsd;
	time_t current;
	char info[100];

	for(i=0; i<MAX_USER_INVITE && (myInvite.list[i]!=-1); ++i) //Parcour la liste des invités
	{
		current=time(NULL);
		if((current - myInvite.timeList[i])>timeOut)
		{
			nsd=myInvite.list[i];
			myInvite.timeList[i]=-1;
	
			for(j=0; j<MAX_THREAD_ALLOWED && (myThread.descriptorList[j]!=nsd); ++j); //Recherche du thread en question avec l'aide du socket descriptor client
			pthread_cancel(myThread.list[j]); //Force la fin du thread
			close(myInvite.list[i]); //Fermeture du client
			sprintf(info, "CheckTimeOut() function:: client socket=%d has been kicked from the server! (time out) ", myInvite.list[i]);
			sendMSG(msg.fd, info);
		}
	}
}

/*Le programme principal*/
int main()
{
	signal(SIGINT, inter_sigInt); 

        init_file(&msg);        // Init gestion des erreurs(création du fichier log)
        sendMSG(msg.fd, "\nSERVER start ... "); //On fait parler le serveur ... la fonction sendMSG enregistre aussi l'heure d'écriture du message
	createList(&myList, msg);	

	
        struct sockaddr_in server;
        struct sockaddr_in user;
        sd=socket(AF_INET, SOCK_STREAM, 0);

        server.sin_family=AF_INET;
        server.sin_addr.s_addr=htonl(INADDR_ANY);
        server.sin_port=htons(SERVER_PORT);

        if(bind(sd, (struct sockaddr*)&server, sizeof(server))==-1)
        {
 		sendMSG(msg.fd, "ERROR:: bind() function");
		exit(3);
	}

        if(listen(sd, MAX_USER_INVITE)==-1)
        {
                sendMSG(msg.fd, "ERROR:: listen() function");
		exit(3);
        }

	int i;
	for(i=0; i<MAX_USER_INVITE; ++i)
		myInvite.list[i]=-1;

	for(i=0; i<MAX_THREAD_ALLOWED; ++i)
		myThread.list[i]=0;
	
	for(;;)
	{
		struct sockaddr_in sUser;
		int size_client=sizeof(sUser), nsd=accept(sd, (struct sockaddr*)&sUser, &size_client), i, j;
		char connect[100];
		
		//On vérifie le time out des clients invités et on met à jour la liste
		checkTimeOut();
		
		//On averti qu'un utilisateur se connecte
		sprintf(connect, "\nInitClient() function:: Connexion from user IP=%s on PORT=%d NSD=%d\n", inet_ntoa(sUser.sin_addr), htons(sUser.sin_port), nsd);
		sendMSG(msg.fd, connect);

		/*Lancement d'un thread pour la gestion du client*/
		for(i=0; i<MAX_THREAD_ALLOWED && (myThread.list[i]!=0); ++i);
		if(i<MAX_THREAD_ALLOWED)
		{
			//On a un invité, on le place dans le tableau des invités
			for(j=0; j<MAX_USER_INVITE && (myInvite.list[j]!=-1); ++j); //Recherche une place libre
			if(j<MAX_USER_INVITE) //Si il y a de la place dans la liste des invités
			{
				//Sauvegarde de l'invité
				myInvite.list[j]=nsd;
				myInvite.addr[j]=sUser;
				myInvite.timeList[j]=time(NULL);

				//Lancement du thread
				pthread_t client;	
				pthread_create(&client, NULL, manageClient, (void*)nsd);
			
				//Sauvegarde du thread
				myThread.list[i]=client;
				myThread.descriptorList[i]=nsd;
			}
			else
			{
				sprintf(connect, "Cannot accept more client! Invite list is full! ");
				sendMSG(msg.fd, connect);
			}
		}
		else
		{
			sprintf(connect, "Cannot accept more client! Thread list full! ");
			sendMSG(msg.fd, connect);
		}
	}
}
